import dk.brics.automaton.Automaton;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.State;
import network.Device;
import network.Network;
import network.Port;
import networkLoader.FBNetwork;
import org.jgrapht.Graph;
import org.jgrapht.Graphs;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedMultigraph;
import org.jgrapht.graph.SimpleGraph;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.stream.Collectors;

class Automata {
    private HashSet<Integer> states;
    private HashSet<String> symbols;
    private HashSet<Integer> finals;
    private HashMap<Integer, HashMap<String, Integer>> transfers;

    public Automata(HashSet<String> symbols) {
        this.states = new HashSet<>();
        this.symbols = symbols;
        this.finals = new HashSet<>();
        this.transfers = new HashMap<>();
        this.addState(0); // start state
//        this.addState(-1); // error state
    }

    public static Automata fromString(String regex, HashSet<String> symbols) {
        Automata automata = new Automata(symbols);

        String[] elements = regex.split(",");
        int idx = 1;
        Integer lastState = 0;
        for (String element : elements) {
            if (element.equals(".*")) {
                for (String sym : symbols) {
                    automata.addTransfer(lastState, sym, lastState);
                }
            } else if (element.startsWith(".")) { // e.g., .3
                for (int i = 0; i < Integer.parseInt(element.substring(1)); i++) {
                    int newState = idx++;
                    automata.addState(newState);
                    for (String sym : symbols) {
                        automata.addTransfer(lastState, sym, lastState);
                    }
                    lastState = newState;
                }
            } else {
                int newState = idx++;
                automata.addState(newState);
                automata.addTransfer(lastState, element, newState);
                lastState = newState;
            }
        }
        HashSet<Integer> finals = new HashSet<>();
        finals.add(lastState);
        automata.setFinals(finals);
        return automata;
    }

    public void addState(Integer label) {
        this.states.add(label);
        this.transfers.put(label, new HashMap<>());

        // we do not need to traverse the automata, DFA is not needed
        // set all transfer to error state, will be overwritten by addTransfer
//        for (String sym : symbols) {
//            this.transfers.get(label).put(sym, -1);
//        }
    }

    public void addTransfer(Integer src, String symbol, Integer dst) {
        this.transfers.get(src).put(symbol, dst);
    }

    public HashSet<Integer> getStates() {
        return states;
    }

    public int takeActionOnState(int state, String action) {
        return this.transfers.get(state).getOrDefault(action, -1);
    }

    public HashSet<Integer> getFinals() {
        return finals;
    }

    public void setFinals(HashSet<Integer> finals) {
        this.finals = finals;
    }

    public static void main(String[] args) {
        HashSet<String> symbols = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
        Automata a = Automata.fromString("a,.*,d", symbols);
        long s = System.nanoTime();
        for (int i = 0; i < 1; i++) {
            Automata b = Automata.fromString("a,b,d", symbols);
//            Automata ab = a.product(b);
//            System.out.println(ab.checkValid());
        }
        System.out.println(System.nanoTime() - s);
    }
}

public class PGTest {
    public static void miniTest() {
        Graph<String, DefaultEdge> networkGraph = new SimpleGraph<>(DefaultEdge.class);
        networkGraph.addVertex("A");
        networkGraph.addVertex("B");
        networkGraph.addVertex("C");
        networkGraph.addEdge("A", "B");
        networkGraph.addEdge("B", "C");
        networkGraph.addEdge("C", "A");
        Automata automata = Automata.fromString("A,.*,C", new HashSet<>(Arrays.asList("A", "B", "C")));
        Graph<PGNode, DefaultEdge> r = product(networkGraph, automata);

        System.out.println(r.edgeSet().size());
    }
    public static void main(String[] args) {
//        miniTest();System.exit(1);
        Network network = FBNetwork.getNetwork();
        Graph<String, DefaultEdge> networkGraph = networkToGraph(network);
        HashSet<String> symbols = network.getAllDevices().stream().map(Device::getName).collect(Collectors.toCollection(HashSet::new));
        Automata a = Automata.fromString("rsw-0-0,.3,rsw-110-0", symbols);
//        Automata a = Automata.fromString("rsw-0-0,.*,fsw-0-0,.*,rsw-1-0", symbols);
        System.out.println("#states in automata: " + a.getStates().size());
        long s = System.nanoTime();
        Graph<PGNode, DefaultEdge> r = product(networkGraph, a);
        System.out.println("#states in pg: " + r.vertexSet().size());
        System.out.println("#states in pg remove: " + new ConnectivityInspector<>(r).connectedSetOf(r.vertexSet().stream().filter(pgNode -> pgNode.getState() == 5 && pgNode.getDevice().equals("rsw-110-0")).findFirst().get()).size());
        System.out.println("#edges in pg: " + r.edgeSet().size());
        System.out.println("product takes: " + (System.nanoTime() - s));
    }

    public static Graph<String, DefaultEdge> networkToGraph(Network network) {
        Graph<String, DefaultEdge> graph = new SimpleGraph<>(DefaultEdge.class);
        for (Device device : network.getAllDevices()) {
            graph.addVertex(device.getName());
        }
        for (Device device : network.getAllDevices()) {
            for (Port port : device.getPorts()) {
                if (port.getPeerDevice() == null) {
                    continue;
                }
                graph.addEdge(device.getName(), port.getPeerDevice().getName());
            }
        }
        return graph;
    }

    public static Graph<PGNode, DefaultEdge> product(Graph<String, DefaultEdge> networkGraph, Automata automaton) {
        Graph<PGNode, DefaultEdge> graph = new DirectedMultigraph<>(DefaultEdge.class);
        for (String device : networkGraph.vertexSet()) {
            for (Integer state : automaton.getStates()) {
                if (state == 0) continue;
                graph.addVertex(new PGNode(device, state));
            }
        }
        for (PGNode pgNode : graph.vertexSet()) {
            for (PGNode pgNode1 : graph.vertexSet()) {
                if (automaton.takeActionOnState(pgNode.getState(), pgNode1.getDevice()) == pgNode1.getState()) {
                    if (Graphs.neighborSetOf(networkGraph, pgNode.getDevice()).contains(pgNode1.getDevice())) {
                        graph.addEdge(pgNode, pgNode1);
                    }
                }
            }
        }
        return graph;
    }

    public static void removeUnused(Graph<PGNode, DefaultEdge> graph) {

    }

    static class PGNode {
        private String device;
        private int state;

        public PGNode(String device, int state) {
            this.device = device;
            this.state = state;
        }

        public String getDevice() {
            return device;
        }

        public int getState() {
            return state;
        }
    }
}
